package net.goodapp.dao;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.goodapp.model.ColumnInfo;
import net.goodapp.model.PrimaryKeyInfo;
import net.goodapp.model.TableInfo;

public class DatabaseDAO {
	private static final Log logger = LogFactory.getLog(DatabaseDAO.class);
	private DataSource dataSource;

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	public List<String> getTableNames() throws SQLException{
		List<String> tableInfos=new ArrayList<String>();
		Connection connection=null;
		DatabaseMetaData metaData=null;
		ResultSet resultSet=null;
		String schema=null;
		String catalog=null;
		try {
			logger.info("Get metadata with from datasource.");
			connection=dataSource.getConnection();
			metaData=connection.getMetaData();
			//resultSet=metaData.getTables(null, schema, null, new String[]{"TABLE","VIEW"});
			resultSet=metaData.getTables(catalog, schema, null, new String[]{"TABLE"});
			while (resultSet.next()) {
				String tableName = resultSet.getString("TABLE_NAME");
				logger.info("Get table name with ["+tableName+"] from metadata.");
				tableInfos.add(tableName);
			}
		} catch (SQLException e) {
			logger.error(e.getMessage());
			throw e;
		}
		finally {
			if(connection!=null){
				connection.close();
			}
			
			if(resultSet!=null){
				resultSet.close();
			}
		}

		return tableInfos;
	}
	
	public List<String> getTableNames(DataSource dataSource) throws SQLException{
		this.dataSource=dataSource;
		return getTableNames();
	}
	
	/**
	 * 根据数据库的DatabaseMetaData信息获取数据库中表的信息
	 * @return
	 * @throws SQLException
	 */
	public List<TableInfo> getTableInfos() throws SQLException {
		List<String> tableNames=getTableNames();
		List<TableInfo> tableInfos=new ArrayList<TableInfo>();
		
		Connection connection=null;
		DatabaseMetaData metaData=null;
		ResultSet resultSet=null;
		String schema=null;
		String catalog=null;
		try {
			connection=dataSource.getConnection();
			metaData=connection.getMetaData();
			
			TableInfo tableInfo=null;
			ColumnInfo columnInfo=null;
			for (String tableName : tableNames) {
				logger.info("Get metadata with from DatabaseMetaData.");
				/**
				 获取可在指定类别中使用的表列的描述。 
				仅返回与类别、模式、表和列名称标准匹配的列描述。它们根据 TABLE_CAT、TABLE_SCHEM、TABLE_NAME 和 ORDINAL_POSITION 进行排序。 
				
				每个列描述都有以下列： 
				
				TABLE_CAT String => 表类别（可为 null） 
				TABLE_SCHEM String => 表模式（可为 null） 
				TABLE_NAME String => 表名称 
				COLUMN_NAME String => 列名称 
				DATA_TYPE int => 来自 java.sql.Types 的 SQL 类型 
				TYPE_NAME String => 数据源依赖的类型名称，对于 UDT，该类型名称是完全限定的 
				COLUMN_SIZE int => 列的大小。 
				BUFFER_LENGTH 未被使用。 
				DECIMAL_DIGITS int => 小数部分的位数。对于 DECIMAL_DIGITS 不适用的数据类型，则返回 Null。 
				NUM_PREC_RADIX int => 基数（通常为 10 或 2） 
				NULLABLE int => 是否允许使用 NULL。 
				columnNoNulls - 可能不允许使用 NULL 值 
				columnNullable - 明确允许使用 NULL 值 
				columnNullableUnknown - 不知道是否可使用 null 
				REMARKS String => 描述列的注释（可为 null） 
				COLUMN_DEF String => 该列的默认值，当值在单引号内时应被解释为一个字符串（可为 null） 
				SQL_DATA_TYPE int => 未使用 
				SQL_DATETIME_SUB int => 未使用 
				CHAR_OCTET_LENGTH int => 对于 char 类型，该长度是列中的最大字节数 
				ORDINAL_POSITION int => 表中的列的索引（从 1 开始） 
				IS_NULLABLE String => ISO 规则用于确定列是否包括 null。 
				YES --- 如果参数可以包括 NULL 
				NO --- 如果参数不可以包括 NULL 
				空字符串 --- 如果不知道参数是否可以包括 null 
				SCOPE_CATLOG String => 表的类别，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） 
				SCOPE_SCHEMA String => 表的模式，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） 
				SCOPE_TABLE String => 表名称，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） 
				SOURCE_DATA_TYPE short => 不同类型或用户生成 Ref 类型、来自 java.sql.Types 的 SQL 类型的源类型（如果 DATA_TYPE 不是 DISTINCT 或用户生成的 REF，则为 null） 
				IS_AUTOINCREMENT String => 指示此列是否自动增加 
				YES --- 如果该列自动增加 
				NO --- 如果该列不自动增加 
				空字符串 --- 如果不能确定该列是否是自动增加参数 
				COLUMN_SIZE 列表示给定列的指定列大小。对于数值数据，这是最大精度。对于字符数据，这是字符长度。对于日期时间数据类型，这是 String 表示形式的字符长度（假定允许的最大小数秒组件的精度）。对于二进制数据，这是字节长度。对于 ROWID 数据类型，这是字节长度。对于列大小不适用的数据类型，则返回 Null。 
				
				
				参数：
				catalog - 类别名称；它必须与存储在数据库中的类别名称匹配；该参数为 "" 表示获取没有类别的那些描述；为 null 则表示该类别名称不应该用于缩小搜索范围
				schemaPattern - 模式名称的模式；它必须与存储在数据库中的模式名称匹配；该参数为 "" 表示获取没有模式的那些描述；为 null 则表示该模式名称不应该用于缩小搜索范围
				tableNamePattern - 表名称模式；它必须与存储在数据库中的表名称匹配
				columnNamePattern - 列名称模式；它必须与存储在数据库中的列名称匹配 
				 */
				resultSet=metaData.getColumns(catalog, schema, tableName, null);
				tableInfo=new TableInfo();
				tableInfo.setTableName(tableName);
				List<PrimaryKeyInfo> primaryKeyInfos=getPrimaryKeyInfos(metaData, catalog, schema, tableName);
				tableInfo.setPrimaryKeyInfos(primaryKeyInfos);
				
				while (resultSet.next()) {
					
					columnInfo=new ColumnInfo();
					columnInfo.setColumnName(resultSet.getString("COLUMN_NAME"));
					columnInfo.setSqlType(resultSet.getInt("DATA_TYPE"));
					columnInfo.setTypeName(resultSet.getString("TYPE_NAME"));
					//columnInfo.setJdbcType(resultSet.getString("DATA_TYPE"));
					columnInfo.setIgnored(false);
					columnInfo.setMaxLength(resultSet.getInt("COLUMN_SIZE"));
					//columnInfo.setOrdinal(resultSet.getInt("ORDINAL_POSITION"));
					columnInfo.setRemarks(resultSet.getString("REMARKS"));
					int nullableValue=resultSet.getInt("NULLABLE");
					if(nullableValue==0){
						columnInfo.setNullable(false);
					}
					else{
						columnInfo.setNullable(true);
					}
					//logger.info("Get column details with <"+columnInfo.getColumName()+"> in table["+tableName+"] from metadata.");
					//System.out.println("Get column details with <"+columnInfo.getColumName()+"> in table["+tableName+"] from metadata.");
					//columnInfo.setPropertyName(StringUtils.);
					tableInfo.getColumnInfos().add(columnInfo);
				}
				logger.info("table["+tableName+"] has "+tableInfo.getColumnInfos().size()+ " columns.");
				tableInfos.add(tableInfo);
			}
		} catch (SQLException e) {
			logger.error(e.getMessage());
			throw e;
		}
		finally {
			if(connection!=null){
				connection.close();
			}
			
			if(resultSet!=null){
				resultSet.close();
			}
		}
		
		return tableInfos;
	}
	
	/**
	 * 根据元数据信息获取每个表的主键信息
	 * @param metaData DatabaseMetaData实例
	 * @param catalog 类别名称；它必须与存储在数据库中的类别名称匹配；该参数为 "" 表示获取没有类别的那些描述；为 null 则表示该类别名称不应该用于缩小搜索范围
	 * @param schema 模式名称；它必须与存储在数据库中的模式名称匹配；该参数为 "" 表示获取没有模式的那些描述；为 null 则表示该模式名称不应该用于缩小搜索范围
	 * @param tableName 表名称；它必须与存储在数据库中的表名称匹配
	 * @return
	 * @throws SQLException
	 */
	public List<PrimaryKeyInfo> getPrimaryKeyInfos(DatabaseMetaData metaData,String catalog,String schema,String tableName) throws SQLException{
		List<PrimaryKeyInfo> primaryKeyInfos=null;
		ResultSet resultSet=null;
		if(metaData!=null){
			primaryKeyInfos=new ArrayList<PrimaryKeyInfo>();
			/*
			 * 获取对给定表的主键列的描述。它们根据 COLUMN_NAME 进行排序。 
			       每个主键列描述都有以下列： 	
			   TABLE_CAT String => 表类别（可为 null） 
			   TABLE_SCHEM String => 表模式（可为 null） 
			   TABLE_NAME String => 表名称 
			   COLUMN_NAME String => 列名称 
			   KEY_SEQ short => 主键中的序列号（值 1 表示主键中的第一列，值 2 表示主键中的第二列）。 
			   PK_NAME String => 主键的名称（可为 null） 	
			       参数：
			   catalog - 类别名称；它必须与存储在数据库中的类别名称匹配；该参数为 "" 表示获取没有类别的那些描述；为 null 则表示该类别名称不应该用于缩小搜索范围
			   schema - 模式名称；它必须与存储在数据库中的模式名称匹配；该参数为 "" 表示获取没有模式的那些描述；为 null 则表示该模式名称不应该用于缩小搜索范围
			   table - 表名称；它必须与存储在数据库中的表名称匹配 
			 */
			resultSet=metaData.getPrimaryKeys(catalog, schema, tableName);
			PrimaryKeyInfo primaryKeyInfo=null;
			while (resultSet.next()) {
				primaryKeyInfo=new PrimaryKeyInfo();
				primaryKeyInfo.setColumnName(resultSet.getString("COLUMN_NAME"));
				primaryKeyInfo.setKeySeq(resultSet.getShort("KEY_SEQ"));
				primaryKeyInfo.setPkName(resultSet.getString("PK_NAME"));
				//logger.info("get primary key ["+primaryKeyInfo.getColumnName()+"] in table ["+tableName+"]");;
				//System.out.println("Table Name="+tableName+",COLUMN_NAME="+resultSet.getString("COLUMN_NAME")+",KEY_SEQ="+resultSet.getString("KEY_SEQ")+",PK_NAME="+resultSet.getString("PK_NAME"));
				primaryKeyInfos.add(primaryKeyInfo);
			}
		}
		return primaryKeyInfos;
	}
}
